/*
 * NetServer - threaded remoting server for scores and datafiles
 *
 * Copyright 2008 James W. Hofmann
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

class NetServer
{
	public static var curversion : Int = 3;
	public static var curday : Int;
	public static var curgame : Int;
	
	public static var s : neko.net.Socket; 
	
	public static function getnum() : Int
	{
		return curgame;
	}

	public static function advanceday()
	{
		var now = Date.now();
		curday = now.getDay();
		curgame = GenNum.todaysgame(Date.now());
		writecurrentgame();
	}

    public static function dbconnect()
	{        
        // initialize the connection
        var useMysql = true;
		var cnx : neko.db.Connection;

		trace("connecting to "+CreepData.databasename+"@"+
		CreepData.databaselocale);

        if( !useMysql )
            cnx = neko.db.Sqlite.open("mydatabase_file.db");
        else {
            cnx = neko.db.Mysql.connect({ 
                host : CreepData.databaselocale,
                port : 3306,
                database : CreepData.databasename,
                user : CreepData.databaselogin,
                pass : CreepData.databasepass,
                socket : null
            });
        }
 
        neko.db.Manager.cnx = cnx;
        neko.db.Manager.initialize();
    }
	
    public static function db_initTable() 
	{
		CreepData.initTable(neko.db.Manager.cnx);
    }

	public static var clients = new List<ClientData>();

	static function initClientApi( scnx : haxe.remoting.SocketConnection, server : neko.net.RemotingServer ) 
	{
		trace("Client connected");
		var c = new ClientData(scnx);
		server.addObject("peer",c);
	}

		static function onClientDisconnected( scnx ) {
		trace("Client disconnected");
	}

	public static function destroyscores()
	{
		neko.db.Manager.cnx.request("DROP TABLE Settings");
		trace("GOODBYE Settings");
		neko.db.Manager.cnx.request("DROP TABLE GameNums");
		trace("GOODBYE GameNums");
		neko.db.Manager.cnx.request("DROP TABLE CreepData");
		trace("GOODBYE CreepData");
		trace("The End");
	}

	public static function getgame(gamenumber : Int)
	{
		var results =
		neko.db.Manager.cnx.request(
		"SELECT datafile, username, groupname, gamenumber FROM"
		+" CreepData WHERE gamenumber!="+
		NetServer.curgame+
		" AND id = "+Std.string(gamenumber)
		);
		if (results.length>0)
		{
			return 					[results.getResult(0),
									 results.getResult(1),
									 results.getResult(2),
									 results.getResult(3)];
		}
		else	
		{
			trace("i got nothing");
			return null;
		}
	}

	public static function getDomainName() : Array<Dynamic>
	{
		var resultdomain : String = "";
		var resultport : Int = 0;

		try
		{
			var f = neko.io.File.read("domainname",false);
			resultdomain = f.readLine();
			resultport = Std.parseInt(f.readLine());
			f.close();		
			trace("loaded domainname");
		}
		catch (d : Dynamic)
		{
			trace("didn't find a valid domainname file!");
			trace("aborting...");
			neko.Sys.exit(1);
		}
		return [resultdomain,resultport];		
	}

	public static function savegame(serialized : String)
	{
		trace("audit!");
		var validnum = getnum();
		CreepData.auditGame(serialized, validnum);
	}

	public static function processindata(indata : StringBuf)
	{

		var str = indata.toString();
		if (str.length>0) // the only thing uploaded to the server
						  // is saved games, so we might as well try
						  // to treat them as such
		{
			savegame(str);
		}
	}

	public static function gennums()
	{
		///// to set up all the game numbers on the database for hs frontend
		trace("ADDING ALL GAME NUMBERS TO DB (this takes a while)");
		var cnx = neko.db.Manager.cnx;
		cnx.request("START TRANSACTION;");
		for (n in 0...GenNum.data.length)
		{
			cnx.request("INSERT INTO GameNums (gamenum) VALUES("+
			Std.string(GenNum.data[n])+");");
		}
		trace("QUERY IN PROGRESS...");
		cnx.request("COMMIT;");		
		trace("GAMES ARE ADDED!");
	
	}
	
	static function main() 
	{

		var args = neko.Sys.args();
		
		switch(args[0])
		{
			default:
				neko.Lib.println("Space Creeps server, format version "
				+curversion);
				neko.Lib.println("Commands:");
				neko.Lib.println("rebuild - generate tables and"+
								 " dump numbers");
				neko.Lib.println("start <domainname> <port> "+
								"- start server");
				neko.Lib.println("destroythescores "+
								"- drop all game-related tables");
				neko.Lib.println("");			
				neko.Lib.println("To start a server from scratch,");
				neko.Lib.println("you need a dbinfo file containing"+
								 " connection info in the format:");			
				neko.Lib.println("db-locale\ndb-login\ndb-pass");			
				neko.Lib.println("db-name");			
				neko.Lib.println("A domainname file containing the");			
				neko.Lib.println("domain the server listens on and");			
				neko.Lib.println("the port it should use,");			
				neko.Lib.println("and a sortednumbers file " +
								 "containing space-seperated " +
								 "integers from 0 to 1000000.");
				neko.Lib.println("Each integer should be a unique"+
								 " value.");
			case("rebuild"):
				CreepData.loadDbInfo();
				GenNum.load();
				trace("loaded numbers");
				dbconnect();
				trace("initializing tables");
				db_initTable();
				trace("inserting numbers");
				gennums();	
			case("start"):	
				CreepData.loadDbInfo();
				GenNum.load();
				trace("loaded numbers");
				dbconnect();

				var curdate = Date.now();
				curday = curdate.getDay();
				curgame = GenNum.todaysgame(Date.now());
				writecurrentgame();

				var s = new neko.net.ThreadRemotingServer();
				s.initClientApi = initClientApi;
				s.clientDisconnected = onClientDisconnected;
				trace("Starting server.");
				var domainname : String;
				var portnum : Int;
				var ar = getDomainName();
				domainname = ar[0];
				portnum = ar[1];
				if(args.length>1)
					domainname = args[1];
				if(args.length>2)
					portnum = Std.parseInt(args[2]);
				trace([domainname, portnum]);
				s.run(domainname, portnum);
			case("destroythescores"):
				CreepData.loadDbInfo();
				dbconnect();
				destroyscores();
		}

		
		
	}

	public static function writecurrentgame()
	{
		neko.db.Manager.cnx.request(
			"UPDATE Settings
			SET value="+Std.string(curgame)+
			" WHERE name='curgame'"
			);
		trace("updated today's game: "+Std.string(curgame));
	}

	public static function updatedl(recid : Int)
	{
		neko.db.Manager.cnx.request(
			"UPDATE CreepData "+ 
			"SET downloads=downloads+1 "+
			" WHERE id="+Std.string(recid)
			);
		trace("incremented download count");
	}
	
}

class Peer extends haxe.remoting.AsyncProxy<PeerApi> {
}

class ClientData implements PeerApi {

	public var peer : Peer;
	public var indata : StringBuf;
	public var outdata : String;
	public var bufferstart : Int;
	public var bufferend : Int;

	public static var parser = new CreepParser();

	public static inline var bytestotransfer = 3000;
	
	public function new( scnx : haxe.remoting.SocketConnection) {

		NetServer.advanceday(); // make sure the current game is updated
							    // this is inefficient...but I'm having
							    // trouble doing fancier stuff!

		peer = new Peer(scnx.FileClient.inst);
		(cast scnx).__private = this;
		outdata = null;
		indata = new StringBuf();
	}

	public function pull( data : String, lastpart : Bool )
	{
		if (indata==null)
		{
			indata = new StringBuf();
			indata.add(data);
		}
		else
		{
			indata.add(data);
		}
		if (lastpart)
		{
			downloadcomplete();
		}
		else
		{
			peer.push();
		}
	}

	public function push()
	{
		if (bufferend<outdata.length)
		{
			bufferend+=bytestotransfer;
			peer.pull(outdata.substr(bufferstart,bytestotransfer),false);
			bufferstart+=bytestotransfer;
		}
		else
		{
			peer.pull(outdata.substr(bufferstart),true);		
		}
	}
	
	public function uploadrequest(requestcode : Int, requestcontent :
									Dynamic)
	{
		outdata = 
			parser.parse(requestcode, requestcontent);
		beginupload();
	}

	function beginupload()
	{
		trace("startupreq");
		bufferstart = 0;
		bufferend = 0;
		push();		
	}

	public function downloadcomplete(?echoed : Bool)
	{
		if (!echoed)
		{
			peer.uploadcomplete(true);
			neko.Lib.println("download finished");
			NetServer.processindata(indata);
			indata = new StringBuf();
		}		
	}
	
	public function uploadcomplete(?echoed : Bool)
	{
		if (!echoed)
		{
			peer.downloadcomplete(true);
			neko.Lib.println("upload finished");
			outdata = null;
		}
	}
	
	public function downloadrequest(requestcode : Int, requestcontent :
									Dynamic)
	{
		trace("startdownreq");
		indata = new StringBuf();
		peer.uploadrequest(requestcode, requestcontent);
	}

	public static function 
	ofConnection( scnx : haxe.remoting.SocketConnection ) : ClientData 
	{
		return (cast scnx).__private;
	}

}
